浏览器常驻的线程
    js引擎线程 		（解释执行js代码、用户输入、网络请求）
    GUI线程 			（绘制用户界面、与js主线程是互斥的）
    http网络请求线程 （处理用户的get、post等请求，等返回结果后将回调函数推入任务队列）
    定时触发器线程 	（setTimeout、setInterval等待时间结束后把执行函数推入任务队列中）
    浏览器事件处理线程 (将click、mouse等交互事件发生后将这些事件放入事件队列中)

Js执行机制
    同步和异步任务分别进入不同的执行"场所"，同步的进入主线程，异步的进入Event Table并注册函数。
    当指定的事情完成时，Event Table会将这个函数移入Event Queue。
    主线程内的任务执行完毕为空，会去Event Queue读取对应的函数，进入主线程执行。
    上述过程会不断重复，也就是常说的Event Loop(事件循环)。

同步任务
    0.代码没有执行的时候，执行栈为空栈
    1.foo函数执行时，创建了一帧，这帧中包含了形参、局部变量（预编译过程），然后把这一帧压入栈中
    2.然后执行foo函数内代码，执行bar函数
    3.创建新帧，同样有形参、局部变量，压入栈中
    4.bar函数执行完毕，弹出栈
    5.foo函数执行完毕，弹出栈
    6.执行栈为空
例:
<script>
    function foo(ot){
        function bar(it){
            console.log(it)
        }
        bar(20)
        console.log(ot)
    }
    foo(10);
</script>

异步任务
	Ajax 进入Event Table，注册回调函数success
	执行console.log('run')
	ajax事件完成http网络请求线程把任务放入Event Queue中
	主线程（调用栈）读取任务下执行success函数
例:
<script type="text/javascript">
	$.ajax({
		url: 'localhost:/js/demo.json',
		data: {},
		success: function (data) {
			console.log(data);
		}
	});
	console.log('run');
</script>

重新理解定时器
	setTimeout的等待时间结束后并不是直接执行的而是先推入浏览器的一个任务队列，在同步队列结束后在依次调用任务队列中的任务。
	setTimeout(function(){}, 0)Js主线程中的执行栈为空时，0毫秒实际上也达不到的，根据HTML标准，最低4毫秒。
	setInterval是每隔一段时间把任务放到Event Queue之中

定时器
<script type="text/javascript">
	var startTime = +new Date();
	function sleep(time){
		for(var i = 0; i < time; i++){
			console.log(i);
		}
	}

	setTimeout(function(){
		var time = +new Date() - startTime;
		console.log('setTimeout ' + time);	//setTimeout 2546, 	一旦系统发现实际执行的时间超过了设定的时间将会立即执行,不会再等待setTimeout里的等待时间
	}, 100);
	sleep(5000); 
	var time = +new Date() - startTime;
	console.log('usual ' + time);			//usual 2540
</script>

*********************************
*			   					*
*	手动封装call、apply、bind  	 *
*								*
*********************************
<script type="text/javascript">
	//this指向
	var value = 'window'
	var obj = {
		value: 'obj'
	}
	function show(){
		console.log(this.value);
	}
	show();				//window
	show.call(obj);		//obj  相当于	obj.show = show; 
						//				obj.show(); 
						//				delete obj.show;
	show.call(null);	//window
</script>
call:
<script type="text/javascript">
	var value = 'window'
	var obj = {
		value: 'obj'
	}
	function show(name, age){
		console.log(this.value);
		return {
			name: name,
			age: age
		}
	}
	// 不传参时的结构:
	// Function.prototype.newCall = function(ctx){	
	// 	ctx.fn = this;							//例:执行代码 show.newCall(obj); 此时对于newCall函数里的this来说,会指向show。
	//											//这句话的意思是给ctx上添加一个名为fn的函数,这个函数的函数体是this(使用命名空间的方式给fn赋值)
	// 											//相当于给obj上添加了一个名为fn函数,这个函数的实际函数体是this(this指向show),也就是给obj上添加了show函数
	// 	ctx.fn();								//执行ctx上的fn函数(执行obj上的show函数)
	// 	delete ctx.fn 							//删除ctx上的fn函数(删除obj上的show函数)
	// }

	//使用eval传参数:
	Function.prototype.newCall = function(ctx){
		let ctx = Object.prototype.toString.call(arguments[0]) == '[object Object]' ? arguments[0]: window;		//例show.call(obj,,,);	ctx相当于obj
		ctx.fn = this							//例show.call(obj);	this指向show,这句话的意思是给ctx上添加一个名为fn的函数,这个函数的函数体是this
		let args = []
		let len = arguments.length
		for(let i = 1; i < len; i++){			//从第2个arguments开始,是需要传入到ctx.fn里的参数
			args.push('arguments[' + i + ']')
		}
		//args = ['arguments[1]', 'arguments[2]'].join(',')
		let result = eval('ctx.fn(' + args.join(',') + ')')
		delete ctx.fn 							
		return result;
	}

	var res = show.newcall(obj, 10, 18);		//obj
</script>
apply:
<script type="text/javascript">
	Function.prototype.newApply = function(ctx, arr){
		let ctx = Object.prototype.toString.call(arguments[0]) == '[object Object]' ? arguments[0]: window;
		ctx.fn = this
		if(!arr){
			let result = ctx.fn()
			deletet ctx.fn
			return result
		}else{
			let args = []
			let len = arr.length
			for(let i = 0; i < len; i++){		//从第2个arguments开始,是需要传入到ctx.fn里的参数
				args.push('arguments[' + i + ']')
			}
			let result = eval('ctx.fn(' + args.join(',') + ')')
			delete ctx.fn 							
			return result
		}
	}
</script>
bind:
<script type="text/javascript">
	Function.prototype.myBind = function(target){
		//arguments[0]
		var target = Object.prototype.toString.call(arguments[0]) == '[object Object]' ? arguments[0]: window;
		//this -> show
		var self = this;			//处理myBind后的函数空执行的情况(被windows调用)
									//例:var newShow = show.myBind(obj, 'lyc'); newShow(18);		---> 	['lyc']
		var args = [].slice.call(arguments, 1);

		var temp = function(){};	//采取中间嵌套空函数的方式,让实例化后的myBind函数能继承原函数的原型,而不是后面绑定的obj对象上的原型
		temp.prototype = this.prototype;

		var F = function(){

			var _arg = [].slice.call(arguments, 0);
			return self.apply(this instanceof temp ? this : target, args.concat(_arg));	
			//这样写是为了处理new show.myBind(obj)的情况:在执行var newShow = show.myBind(obj) 语句时,此时newShow 被赋值为,由bind函数返回的function F(){...}

			//情况一： 然后如果执行newShow(18);则相当于空执行function F(){...}此时this指向window,执行this instanceof temp ?时会返回false
			//所以在执行到this instanceof temp ?判断时,会执行return self.apply(target, args.concat(_arg));
			//即返回一个由target对象调用的,传入了args.concat(_arg)参数后的,self函数(也就是show函数)的执行结果

			//情况二： 如果执行new newShow(18);则相当于执行new function F(){...},所以此时this指向实例化后的function F(){...}函数,并且又有F.prototype = new temp();
			//所以有this instanceof F 为true,并且又有F.prototype instanceof temp,所以在执行到this instanceof temp ?判断时会返回true
			//所以会执行return self.apply(this, args.concat(_arg));并且此时this指向实例化后的function F(){...}函数,
			//即理论上应该返回一个由this(也就是实例化的function F(){...}对象)调用的,传入了args.concat(_arg)参数后的,self函数(也就是show函数)的执行结果
			//但由于执行的是new function F(){...},也就是在实例化一个构造函数,
			//这样的话,如果返回了五种简单数据类型,则会默认返回一个实例化后的function F(){...}对象,而覆盖手动写入的return值
			//比如:function A(){return 2};var a = new A();控制台输入a,会返回A{},
			//所以在执行os = new newShow(18)时,此时os不会等于写在这里的return self.apply(this, args.concat(_arg));
			//而是os = 新的实例化后的function F(){...},又有F.prototype = new temp();temp.prototype = this.prototype;(this指向show)
			//所以os会继承一切来自show原型上的东西,即os.lastName = 'Li'
		}
		F.prototype = new temp();
		return F;
	}
	//测试:
	var obj = {
		value: 1
	}
	function show(name, age){
		console.log(this)	
		console.log(this.value)
		console.log(name, age)
		return name + age + this.value;		
	}
	function FatherShow(){}
	FatherShow.prototype.lastName = 'Li';
	show.prototype = new FatherShow();

	var newShow = show.myBind(obj, 'lyc');
	newShow(18);						//Object {value: 1}(this)	//1(this.value)		//lyc 18(name,age)
	console.log(newShow(18));			//Object {value: 1}(this)	//1(this.value)		//lyc 18(name,age)	//lyc181(return)
	new newShow(18)						//F {} 		//undefined 	//lyc 18
	console.log(new newShow(18));		//F {} 		//undefined 	//lyc 18	//F {}

	var os = new newShow(18);
	console.log(os.lastName);			//Li
</script>
*****************
*			    *
*	柯里化函数  	*
*				*
*****************
<script type="text/javascript">
调用形式:
	function add(a,b,c){} ; 
	var newAdd = Curry(add);
	//newAdd(1, 2, 3);
	//newAdd(1)(2)(3);
	//newAdd(1, 2)(3);
	//newAdd(1)(2, 3);
例:
	//柯里化前奏,需要固定数量的参数:
	//fixed parmas 4, 固定4个参数的函数add
	function add(a, b, c, d){
		return a + b + c + d;
	}
	function FixedParmasCurry(fn){
		//此时传入的实参为[add, 1, 2];
		var _arg = [].slice.call(arguments, 1); 	//意思是arguments类数组调用数组的slice方法,从第2位开始截取一直截到最后,储存到_arg中
		return function(){
			//写在这里面的东西是调用FixedParmasCurry函数后所返回的东西
			//对于执行了FixedParmasCurry(add, 1, 2)后的newAdd函数来说,执行newAdd(3, 4)时arguments = [3, 4]
			var newArg = _arg.concat([].slice.call(arguments, 0))		//把之前截取的_arg数组与现在截取的arguments数组合并,形成最终的需要传入add的参数列表
			return fn.apply(this, newArg);
		}
	}
	var newAdd = FixedParmasCurry(add, 1, 2);
	console.log(newAdd(3, 4));

	//柯里化函数:
	//look forward to Params(期望把参数凑齐的函数)
	function add(a, b, c, d){
		return a + b + c + d;
	}
	function FixedParmasCurry(fn){
		var _arg = [].slice.call(arguments, 1); //意思是arguments类数组调用数组的slice方法,从第2位开始截取一直截到最后,储存到_arg中
		return function(){
			//写在这里面的东西是调用FixedParmasCurry函数后所返回的东西
			//对于执行了FixedParmasCurry(add, 1, 2)后的newAdd函数来说,执行newAdd(3, 4)时arguments = [3, 4]
			var newArg = _arg.concat([].slice.call(arguments, 0))		//把之前截取的_arg数组与现在截取的arguments数组合并,形成最终的需要传入add的参数列表
			return fn.apply(this, newArg);
		}
	}
	function Curry(fn, length){
		var length = length || fn.length;		//需要参数的个数, fn.length表示的是fn函数需要传入的实参个数
		return function(){
			if(arguments.length < length){		//凑不齐的话继续凑,把已经传过来的参数先处理,然后返回一个需要传入参数减少了的柯里化函数
				var combined = [fn].concat([].slice.call(arguments , 0));							//把fn函数和没凑齐的参数连接起来组成combined,下一步会传入到FixedParmasCurry中
				return Curry(FixedParmasCurry.apply(this, combined), length - arguments.length);	//返回一个必要参数减少了的柯里化函数
			}else{								//参数凑齐了就执行
				return fn.apply(this, arguments)
			}
		}
	}
	var newAdd = Curry(add);
	var na1 = newAdd(1);

	var na11 = na1(2);
	console.log(na11(2, 3))		//8

	var na12 = na1(3);
	console.log(na12(2, 3, 4))	//9, 参数传多了也无所谓,自动忽略多余的参数

	//柯里化应用:
	function ajax(type, url, data){
		var xhr = new XMLHttpRequest();
		xhr.open(type, url, true);
		xhr.send(data);
	}
	//虽然ajax这个函数非常通用,但在重复调用的时候参数冗余
	ajax.('POST', 'www.test1.com', 'name = kevien')
	ajax.('POST', 'www.test2.com', 'name = kevien')
	ajax.('POST', 'www.test3.com', 'name = kevien')
	//利用curry
	var ajaxCurry = curry(ajax);
	//以POST类型请求数据为例:
	var post = ajaxCurry('POST');
	post('www.test.com', 'name = kevien');
	//以POST类型请求来自于www.test.com的数据
	var postFormTest = post('www.test.com');
	postFormTest('name = kevien');
</script>
*****************
*			    *
*	数组扁平化  	*
*				*
*****************
<script type="text/javascript">
	//————————push 直接改变当前数组；concat 不改变当前数组————————————//

	var arr = [1,2,[3,5,[5,6],['d'],6,[5,6,8]],6]
	//方法1
	function isArray(obj){
		return Object.prototype.toString.call(obj) == '[object Array]'
	}
	function f1(arr){
		var arr = arr || [],
			resArr = [],
			len = arr.length;
			for(var i = 0; i < len; i++){
				if(isArray(arr[i])){
					resArr = resArr.concat(f1(arr[i]))
				}else{
					resArr.push(arr[i]);
				}
			}
			return resArr;
	}
	//方法2
	Array.prototype.f2 = function(){
		var resArr = [];
		this.forEach(function(item){
			Object.prototype.toString.call(item) == '[object Array]' ? resArr = resArr.concat(item.f2()) : resArr.push(item)
		})
		return resArr;
	}
	//方法3
	function f3(arr){
		var arr = arr || [];
		return arr.reduce(function(pre, next, index, arr){
			return Object.prototype.toString.call(next) == '[object Array]' ? pre.concat(f3(next)) : pre.concat(next); 	
			//因为reduce函数是最后返回一个最终的值,不会每执行一步都返回值所以要使用concat,而不能使用push
		}, []);		//reduce传参形式为reduce(cb, init),cb是回调函数,init是初始化值
	}

	//方法4
	const f4 = arr => arr.reduce((pre, next, index, arr) => Object.prototype.toString.call(next) == '[object Array]' ? pre.concat(f3(next)) : pre.concat(next), [])

	//测试
	console.log(f1(arr));		//[1, 2, 3, 5, 5, 6, "d", 6, 5, 6, 8, 6]
	console.log(arr.f2());		//[1, 2, 3, 5, 5, 6, "d", 6, 5, 6, 8, 6]
	console.log(f3(arr));		//[1, 2, 3, 5, 5, 6, "d", 6, 5, 6, 8, 6]
	console.log(f4(arr));		//[1, 2, 3, 5, 5, 6, "d", 6, 5, 6, 8, 6]
</script>
*****************
*			    *
*	惰性函数  	*
*				*
*****************
<script type="text/javascript">
	//要求:写一个 test 函数，这个函数返回首次调用时的 new Date().getTime()时间，注意是首次
	var test = function(){
		var t = new Date().getTime();
		test = function(){				//更改windows下的test函数
			return t;
		}
		return test();
	}
	console.log(test());		//1543151647308
	console.log(test());		//1543151647308
	console.log(test());		//1543151647308
</script>

<html>
<!-- 	应用:封装对绑定函数的兼容性处理 -->
	<div id="demo" style="width: 100px; height: 100px; background-color: red"></div>
	<script type="text/javascript">
		function addEvent(dom, type, handler){
			if(dom.addEventListener){
				console.log('first');
				dom.addEventListener(type, handler, false);
				addEvent = function(dom, type, handler){
					console.log('last');
					dom.addEventListener(type, handler, false);
				}
			}else{
				console.log('first');
				dom.attachEvent('on' + type, handler);
				addEvent = function(dom, type, handler){
					console.log('last');
					dom.attachEventListener('on' + type, handler);
				}
			}
		}
		addEvent(demo, 'click', function(){
			alert(0);						//first,说明第一次绑定时执行了if判断;		
		});
		addEvent(demo, 'click', function(){
			alert(1);						//last,说明再次绑定时并没有执行if判断;
		});	
		addEvent(demo, 'click', function(){
			alert(2);						//last,说明再次绑定时并没有执行if判断;
		});			
	</script>
</html>
*****************
*			    *
*	函数组合应用 *
*				*
*****************
<script type="text/javascript">
	//由若干个函数组成一个新的函数,同时形成数据传递的这样一个函数
	//举例说明函数组合大概的形式
	function compose(f, g){		//这个函数的返回值会先执行g(),然后再执行f(),是自右向左执行的,所以称之为左倾函数
		return function(x){
			return f(g(x))
		}
	}
	function toUpperCase(str){
		return str.toUpperCase()
	}
	function add(str){
		return str + '!'
	}
	var f = compose(add, toUpperCase)
	console.log(f('time'))				//TIME!

	//把compose写成真正的函数组合形式(普通形式)
	function compose1(){
		var args = Array.prototype.slice.call(arguments)	//把arguments类数组转为数组
		var len = args.length - 1;
		return function(x){
			var result = args[len](x)
			while(len--){						//如果len = 1,则while(1),执行while循环
				result = args[len](result)		//然后result = args[0](args[1](x)) ----->'len--'是先用后减
			}
			return result
		}
	}
	//ES5 reduce形式:
	function compose2(){
		var args = Array.prototype.slice.call(arguments)
		return function(x){
			return args.reduceRight(function(res, cb){ 	//-->reduce/reduceRight传参形式:(preValue, ele, index, array),这里的cb就是args里的ele
				return cb(res);
			}, x)					//x是传入的初始值
		}
	}
	//ES6:
	const compose3 = (...args) => x => args.reduceRight((res, cb) => cb(res), x)
	//先对传入的实参解构赋值为args,然后返回{x => args.reduceRight((res, cb) => cb(res), x)}这个整体,这个整体需要由后续函数手动调用,也就是c('time')
	//所以在c('time')里,x就是'time'

	//测试
	function toUpperCase(str){		//变大写
		return str.toUpperCase()
	}
	function add(str){				//加'!'
		return str + '!'
	}
	function split(str){			//打散成数组
		return str.split('')
	}
	function reverse(arr){			//数组逆序
		return arr.reverse()
	}
	function join(arr){				//数组通过'-'组合成为字符串
		return arr.join('-')
	}

	var a = compose1(add, join, reverse, split, toUpperCase)
	console.log(a('time'))			//E-M-I-T!	
	var b = compose2(add, join, reverse, split, toUpperCase)
	console.log(b('time'))			//E-M-I-T!
	var c = compose3(add, join, reverse, split, toUpperCase)
	console.log(c('time'))			//E-M-I-T!
</script>
*************
*			*
*  函数记忆 	*
*			*
*************
<script type="text/javascript">
	function test(n){
		var res = 0;
		for(i = 0; i < 10000000; i++){
			res += n;
		}
		return res;
	}
	function memorize(fn){
		var cache = {}			//必须要把cache的声明写在return function(fn){...}外,因为只有把cache写在return function(fn){...}的父级上才能起到缓存的效果
		return function(){
			var key = arguments.length + Array.prototype.join.call(arguments) 	//用来作为传参的唯一标识
			if(cache[key]){														//如果缓存里有,就直接返回
				return cache[key]
			}else{
				cache[key] = fn.apply(this, arguments)
				return cache[key]
			}
		}
	}
	var newF = memorize(test)
	//测试
	console.time('fir M');
	console.log(newF(1));		//10000000
	console.timeEnd('fir M');	//fir M: 24.805ms

	console.time('fir noM');
	console.log(test(1));		//10000000
	console.timeEnd('fir noM');	//fir noM: 27.996ms

	console.time('sec M');
	console.log(newF(1));		//10000000
	console.timeEnd('sec M');	//sec M: 0.225ms			//速度明显快很多

	console.time('sec noM');
	console.log(test(1));		//10000000
	console.timeEnd('sec noM');	//sec noM: 26.586ms			//对比没有记忆的函数会慢很多
</script>
*************************
*						*
*  拖拽&计算覆盖面积函数 	*
*						*
*************************
<body>
	<div class="wrapper" style="width: 500px; height: 500px; border: 1px solid black;">
		<div id="oBox" style="width: 100px; height: 100px; background-color: red; position: absolute; left: 8px; top: 8px; cursor: move;"></div>
		<div id="fixed" style="width: 100px; height: 100px; background-color: green; position: absolute; left: 250px; top: 150px; z-index: -1" ></div>
	</div>
	<script type="text/javascript">
		var wrapper = document.getElementsByTagName('div')[0];
		var ele = document.getElementById('oBox');
		var fixed = document.getElementById('fixed');
		//拖拽函数
		function drag(ele, wrap, flag){			//flag为true时,只要鼠标脱离wrap区域就立即终止拖拽状态(无论脱离时鼠标是否处于mousedown状态),
												//flag为false时,鼠标脱离wrap区域不会终止拖拽状态(此时只有onmouseup事件可以终止拖拽状态),
												//鼠标在mousedown状态脱离wrap区域时只要不触发onmouseup事件,再次回到wrap区域ele依然会跟着动
			//down --> move --> up
			var disX, disY;
			var dragStat = false;				//判断当前元素是否正处于拖拽状态中, 默认dragStat = false,即不是处在drag事件中

			ele.onmousedown = function(e){
				var event = e || window.event;
				dragStat = true;
				disX = event.offsetX;
				disY = event.offsetY;
			};

			function whichMode(flag){			//判断是哪种模式
				if(flag){
					return dragStat = false;
				}
			}

			wrap.onmousemove = function(e){		//ele元素必须要在wrap区域内移动才可以
				var event = e || window.event;
				if(dragStat){
					var curX, curY, minX, minY, eleW, eleH, wrapW, wrapH, endX, endY;
						curX = event.clientX - disX;
						curY = event.clientY - disY;
						minX = this.offsetLeft;
						minY = this.offsetTop;
						eleW = ele.offsetWidth;
						eleH = ele.offsetHeight;
						wrapW = this.offsetWidth;
						wrapH = this.offsetHeight;

					endX = curX < minX ? minX : minX + wrapW < curX + eleW ? minX + wrapW - eleW : curX;
					endY = curY < minY ? minY : minY + wrapH < curY + eleH ? minY + wrapH - eleH : curY;

					ele.style.left = endX + 'px';
					ele.style.top = endY + 'px';
					if(endX != curX || endY != curY){
						whichMode(flag);	
					}
					coveredArea(ele, fixed);
				}
			}
			document.onmouseup = function(){
				dragStat = false;
			};
		}

		//计算覆盖面积函数
		function coveredArea(moved, fixed){
			var movedL,movedT,movedH,movedW,fixedL,fixedT,fixedH,fixedW,coveredH,coveredW;
			movedL = moved.offsetLeft;
			movedT = moved.offsetTop;
			movedH = moved.offsetHeight;
			movedW = moved.offsetWidth;
			fixedL = fixed.offsetLeft;
			fixedT = fixed.offsetTop;
			fixedH = fixed.offsetHeight;
			fixedW = fixed.offsetWidth;

			coveredW = fixedL <= movedL && movedL <= fixedL + fixedW ? fixedL + fixedW - movedL : fixedL <= movedL + movedW && movedL + movedW <= fixedL + fixedW ? movedL + movedW - fixedL: 0;
			coveredH = fixedT <= movedT && movedT <= fixedT + fixedH ? fixedT + fixedH - movedT : fixedT <= movedT + movedH && movedT + movedH <= fixedT + fixedH ? movedT + movedH - fixedT: 0;

			console.log(coveredW * coveredH);
		}

		drag(ele, wrapper, false);
	</script>
</body>
*************************
*						*
*  canvas中的数学应用 	*
*						*
*************************
<body>
<!-- 	画扇型 -->
	<canvas id="can" width="500px" height="500px"></canvas>		<!-- 宽高各500px的画布 -->
	<div id="box" style="height: 50px; width: 500px; text-align: center; color: green; font-size: 20px; line-height: 50px;"></div>
	<script type="text/javascript">
		var can = document.getElementById('can');
		var ctx = can.getContext('2d');					//声明2d画笔
		var oBox = document.getElementById('box');

		//CanvasRenderingContext2D是一个对象,在原型链上编辑扇形方法(sector)
		CanvasRenderingContext2D.prototype.sector = function(x, y, r, sDeg, eDeg){		
			//(x, y, r, sDeg, eDeg)分别对应圆心点x坐标、y坐标,半径,起始角度,终止角度  
			this.save();					//保存画布当前状态
			this.beginPath();				//开始画
			this.moveTo(x, y);				//把笔移到圆心
			//arc是画部分圆的函数,true/false是逆/顺时针画,对于角度来说传入的实参的弧度制的,所以要把传入的角度转为弧度制单位
			this.arc(x, y, r, sDeg / 180 * Math.PI, eDeg / 180 * Math.PI, false)
			this.closePath();				//结束当前路径
			this.restore();					//保存当前状态,(为画布保存最近的画图状态,恢复save之后的画图状态)
			return this;
		}

		var timer;
		var angle = 0;
		var per;							//声明百分率数字

		ctx.fillStyle = 'red';				//设置填充样式为红色		

		timer = setInterval(function(){
			angle += 5;
			ctx.sector(250, 250, 100, 0, angle).fill();	//先调用sector方法(250,250是相对于canvas画布的相对坐标,0,90是传入的角度),然后调用填充方法
			//angle / 360 = per / 100; -> 5 * angle / 18
			per = (100 * angle / 360).toFixed(2);		//toFixed(2)是保留2位小数
			oBox.innerHTML = '加载中' + per + '%';
			if(angle == 360){
				clearInterval(timer);
				oBox.innerHTML = '加载完成!';
			}
		}, 200)
	</script>
</body>
*************************
*						*
*  公式导出鼠标位置判断 	*
*						*
*************************
<body>
	<div id="box" style="width: 300px; height: 100px; position: absolute; left: 200px; top: 200px; background-color: red;"></div>	
	<script type="text/javascript">
		var oBox = document.getElementById('box');
		//判断鼠标在元素上移入移出的方向(上下左右)
		//mouseenter / mouseleave + direction
		function bindEvent(ele){
			oBox.onmouseenter = function(event){
				get(ele, event, 'in');
			};
			oBox.onmouseleave = function(event){
				get(ele, event, 'out');
			}
		}
		function get(ele, event, state){
			var d = getDir(ele, event)
			var dir = '';
			switch(d){
				case 0:
					dir = '-top';
					break;
				case 1:
					dir = '-right';
					break;
				case 2:
					dir = '-bottom';
					break;
				case 3:
					dir = '-left';
					break;
			}
			console.log(state + dir);
		}
		function getDir(ele, event){
			var w, h, l, t, x, y, angle, zone, dir;
			w = ele.offsetWidth;
			h = ele.offsetHeight;
			l = ele.offsetLeft;
			t = ele.offsetTop;
			//x与y均是相对于整个ele正中心点的坐标值;如果不是正方形,且宽大于高的话,就让x轴(横坐标)坐标值缩小相应尺寸,以适应y轴(纵坐标)坐标值,这样做的目的是把长方形压缩成正方形来看待
			x = (event.clientX - l - w / 2) * (w > h ? (h / w) : 1);
			y = (event.clientY - t - h / 2) * (h > w ? (w / h) : 1);

			angle = Math.atan2(y, x) / Math.PI * 180 + 180;	//弧度转角度,Math.tan2(y, x)/Math.PI得出的值是 (-1 ~ 1],这样转完后得出的结果范围值是 (0 ~ 360]
															//Math.atan2(0, 1)/Math.PI = 0; Math.atan2(1, 1)/Math.PI = 0.25; Math.atan2(1, 0)/Math.PI = 0.5
															//Math.atan2(1, -1)/Math.PI = 0.75; Math.atan2(0, -1)/Math.PI = 1(也可以看成是-1); Math.atan2(-1, -1)/Math.PI = -0.75;
			zone = Math.round(angle / 90);
			dir = (zone + 3) % 4;
			return dir;
		}

		bindEvent(oBox);
	</script>
</body>
*************************
*						*
*  剖析放大镜中的数学原理 *
*						*
*************************
<body>
	<div class="wrapper" 	 style="width: 500px; height: 500px; position: relative; border: 1px solid black;">
		<div class="imgCont" style="width: 100%;  height: 100%;">
			<div class="img" style="width: 100%;  height: 100%;  background: url(./CSS2.0盒模型.png) 0% 0% no-repeat; background-size:100% 100%;">
				<!-- background: url(./CSS2.0盒模型.png) 0% 0% no-repeat; 里面的 0% 0%是定位值 -->
			</div>
		</div>
		<div class="bigImgZone" 	 style="width: 500px; height: 500px; position: absolute; border: 1px solid black; top: 0; left: 105%; overflow: hidden; display: none;">
				<!-- top: 0; left: 105%";105%是相对于自身的105% -->
		</div>
	</div>
	<script type="text/javascript" src="./jquery-2.0.3.js"></script>
	<script type="text/javascript">
		var originImgZone = document.getElementsByClassName('imgCont')[0],
		    bigImgZone = document.getElementsByClassName('bigImgZone')[0];

		function bigger(originImgZone, moveBox, bigImg, event, mul){		//mul是放大的倍数
			var w, h, maxL, maxT, minL, minT, X, Y, endL, endT;

			w = 500 / mul;					//w与h分别是按比例缩小后的移动区域的宽和高,500是原图的宽和高
			h = 500 / mul;

			maxL = 500 - w;					//maxL与maxT分别是移动区所允许的最大的距离左边线的距离和距离高边线的距离(超过这个值的话,moveBox就超出原图的边线了)
			maxT = 500 - h;
			minL = 0;
			minT = 0;

			X = event.clientX - originImgZone.offset().left - w / 2;
			Y = event.clientY - originImgZone.offset().top - h / 2;

			endL = (X > minL) ? (X < maxL) ? X : maxL : minL;
			endT = (Y > minT) ? (Y < maxT) ? Y : maxT : minT;

			moveBox.css({
				'height': h + 'px',
				'width': w + 'px',
				'display': 'block',
				'left': endL + 'px',
				'top': endT + 'px',
			});
			bigImg.css({
				'display': 'block'
			}).find('img').css({
				'width': mul * 500 + 'px',
				'height': mul * 500 + 'px',
				'margin-left': -endL * mul,
				'margin-top': -endT * mul
			})
		}

		function bindEnlargedEvent(originImgZone, bigImgZone, mul){
			var originImgZone, bigImgZone, moveBox, bigImg;

			    originImgZone = $(originImgZone);
			    bigImgZone = $(bigImgZone);
			    moveBox = $('<div class="moveBox" style="position: absolute; background: rgba(255, 0, 255, 0.3); z-index: 999;cursor: move; display: none;"></div>');
			    bigImg = $('<img src="./CSS2.0盒模型.png" alt="">');
				originImgZone.append(moveBox);
				bigImgZone.append(bigImg);

			originImgZone.on('mousemove', function(event){
				bigger(originImgZone, moveBox, bigImgZone, event, mul)
			}).on('mouseleave', function(event){
				moveBox.hide();
				bigImgZone.hide();
			})
		}

		bindEnlargedEvent(originImgZone, bigImgZone, 3);
	</script>
</body>
******************
*			 	 *
*  联动放大图标	 *
*			 	 *
******************
<body>
	<div class="wrapper" style="position: absolute; bottom: 50px; margin: 0 auto; text-align: center;">
		<a href="javascript:void(0)"><img src="1.png" alt="" style="width: 64px; height: 64px;"></a>
		<a href="javascript:void(0)"><img src="2.png" alt="" style="width: 64px; height: 64px;"></a>
		<a href="javascript:void(0)"><img src="3.png" alt="" style="width: 64px; height: 64px;"></a>
		<a href="javascript:void(0)"><img src="4.png" alt="" style="width: 64px; height: 64px;"></a>
		<a href="javascript:void(0)"><img src="5.png" alt="" style="width: 64px; height: 64px;"></a>
		<a href="javascript:void(0)"><img src="6.png" alt="" style="width: 64px; height: 64px;"></a>
		<a href="javascript:void(0)"><img src="7.png" alt="" style="width: 64px; height: 64px;"></a>
	</div>
	<script type="text/javascript" src="./jquery-2.0.3.js"></script>
	<script type="text/javascript">
		var wrapper = document.getElementsByClassName('wrapper')[0];

		function moveBigger(wrapper){

			var $img, originItemWidth = [], originItemHeight = [];
				
			$img = $(wrapper).find('img');
			for(var i = 0; i < $img.length; i++){
				var $item = $($img[i]);
				originItemWidth.push($item.css('width'));
				originItemHeight.push($item.css('height'));
			}

			function getDis(item){
				var $item, fixedX, fixedY, disX, disY;

				$item = $(item);
				fixedX = parseInt($item.offset().left) + parseInt($item.css('width')) / 2;
				fixedY = parseInt($item.offset().top) + parseInt($item.css('height')) / 2;
				disX = Math.abs(event.clientX - fixedX);
				disY = Math.abs(event.clientY - fixedY);

				return Math.sqrt(Math.pow(disX, 2) + Math.pow(disY, 2))
			}

			$(document).on('mousemove', function(e){
				var event = e || window.event;
				for(var i = 0; i < $img.length; i++){
					var $item, dis, endDis, originWidth, originHeight, biggerWidth, biggerHeight;

					$item = $($img[i]);
					dis = getDis($img[i]);
					endDis = Math.round(Math.min(dis, 200));
					originWidth = parseInt(originItemWidth[i]);
					originHeight = parseInt(originItemHeight[i]);
					biggerWidth = Math.round((200 - endDis) / 200 * originWidth + originWidth);
					biggerHeight = Math.round((200 - endDis) / 200 * originHeight + originHeight);
					$item.css({
						'width': biggerWidth + 'px',
						'height': biggerHeight + 'px'						
					})
				}
			})
		}
		moveBigger(wrapper);
	</script>
</body>
**************
*			 *
*  正六边形	 *
*			 *
**************
<body>
	<script type="text/javascript" src="./jquery-2.0.3.js"></script>
	<script type="text/javascript">
		function RegularHexagon(x, y, sideLength, color){

			var contSideWidth, contSideHeight, contL, contT, attachHeight, contStyle, contTopStyle, contBottomStyle;
			contSideWidth = sideLength / 2 * 1.732 * 2;
			contSideHeight = sideLength;
			attachHeight = sideLength / 2;			
			contL = x - contSideWidth / 2;
			contT = y - contSideHeight / 2;

			contStyle = `<div class="RegularHexagon" style="position: absolute;height: ${contSideHeight}px;width: ${contSideWidth}px;background-color: ${color};left: ${contL}px;top: ${contT}px;"></div>`;

			contTopStyle = `<style>.RegularHexagon::before{ content: ""; display: block; position: absolute; width: 0; height: 0; border-left: ${contSideWidth/2}px solid transparent; border-right: ${contSideWidth/2}px solid transparent; border-bottom: ${sideLength/2}px solid ${color}; left: 0; top: ${-contSideHeight/2}px;}</style>`;
			
			contBottomStyle = `<style>.RegularHexagon::after{ content: ""; display: block; position: absolute; width: 0; height: 0; border-left: ${contSideWidth/2}px solid transparent; border-right: ${contSideWidth/2}px solid transparent; border-top: ${sideLength/2}px solid ${color}; left: 0; top: ${contSideHeight}px;}</style>`;

			cont = $(contStyle);
			cont.append(contTopStyle).append(contBottomStyle);

			$(document.body).append(cont);
		}
		RegularHexagon(500, 100, 100, 'red');
	</script>
</body>
**************
*			 *
*   红包雨	 *
*			 *
**************
<body>
	<div class="wrapper" style="position: absolute; width: 500px; height: 500px; border: 1px solid black; left: 50%; margin-left: -300px; top: 50px"></div>
	<script type="text/javascript" src="./jquery-2.0.3.js"></script>
	<script type="text/javascript">
		var wrapper = document.getElementsByClassName('wrapper')[0],
			img1 = './1.png',
			img2 = './2.png',
			img3 = './3.png',
			img4 = './4.png',
			img5 = './5.png',
			img6 = './6.png';

		function itemRain(wrapper, imgsSrcArr){
			var $wrapper, wrapperTop, wrapperLeft, wrapperWidth, wrapperHeight, imgsStyleArr, randomInteger;

			$wrapper = $(wrapper);
			wrapperTop = $wrapper.offset().top;
			wrapperLeft = $wrapper.offset().left;
			wrapperWidth = parseInt($wrapper.css('width'));
			wrapperHeight = parseInt($wrapper.css('height'));
			imgsStyleArr = imgsSrcArr.map((items) => '<img src=' + items + ' style="position: absolute; width: 80px; height: 80px;">')
			randomInteger = (min, max) => Math.round(min + Math.random() * (max - min));

			function down(div){
				div.animate({
					top: wrapperHeight - 80,
				}, 3000, function() {
					$(div).remove();
				})
			}

			setInterval(function(){
				var $oDiv = $(imgsStyleArr[randomInteger(0, imgsStyleArr.length - 1)]);
				$oDiv.css({
					'top': randomInteger( -50, 0),
					'left': randomInteger(0, wrapperWidth - 80),
					'z-index': 10,
					'transform': 'rotate(' + randomInteger(-30, 30) + 'deg)'
				}).appendTo($wrapper);
				down($oDiv);
			}, randomInteger(400, 600))
		}
		itemRain(wrapper, [img1, img2, img3, img4, img5, img6]);
	</script>
</body>
*************************
*			 			*
*  不使用jquery模拟下雪	*
*			 			*
*************************
<body>
	<div class="wrapper" style="position: absolute; width: 500px; height: 500px; border: 1px solid black; left: 50%; margin-left: -300px; top: 50px;">
	</div>
	<script type="text/javascript">
		var wrapper = document.getElementsByClassName('wrapper')[0],
			snow = './snow.png';

		function itemRain(wrapper, snowImgSrc){
			var wrapperTop, wrapperLeft, wrapperWidth, wrapperHeight, randomInteger;

			wrapperTop = wrapper.offsetTop;
			wrapperLeft = wrapper.offsetLeft;
			wrapperWidth = parseInt(wrapper.offsetWidth);
			wrapperHeight = parseInt(wrapper.offsetHeight); 
			randomInteger = (min, max) => Math.round(min + Math.random() * (max - min));

			function down(div){
				var curTop = div.offsetTop;
				var timer = setInterval(function(){
					curTop += 2;
					console.log(curTop);
					if(curTop > wrapperHeight - 10){
						div.style.display = 'none';
						cancelAnimationFrame(timer);
					}
					div.style.top = curTop + 'px';
				}, 16);
			}
			setInterval(function(){
				var randomTop, randomLeft;
				randomTop = randomInteger( -30, 0);
				randomLeft = randomInteger(0, wrapperWidth - 10);
				snowDom = document.createElement('img');
				snowDom.src = snow;
				snowDom.style.cssText = `position: absolute; width: 10px; height: 10px; top: ${randomTop}px; left: ${randomLeft}px;`;
				wrapper.appendChild(snowDom);
				down(snowDom);
			}, randomInteger(400, 600))
		}
		itemRain(wrapper, snow);
	</script>
</body>